package net.child.chess.core;

import net.child.chess.pieces.Piece;
import net.child.chess.pieces.IMoves;
import net.child.chess.pieces.MoveInfo;
import net.child.chess.utils.ChessDef;
import net.child.chess.utils.Move;
import java.util.Iterator;
import java.util.Stack;
import java.util.List;
import java.util.ArrayList;

public class Board implements IMoves
{
	public Board()
	{
		mPlayerPieces = new PlayerPieces[ChessDef.MAX_PLAYES];
		for(int i=0; i<ChessDef.MAX_PLAYES; ++i)
		{
			mPlayerPieces[i] = new PlayerPieces();
		}
		mPieces = new Piece[ChessDef.NUM_PIECES];
		mMovesHistory = new Stack<List<MoveInfo>>();
		mBoardParams = new BoardParams();
	}
	
	public void reset(BoardParams params)
	{
		for(int i=0; i<ChessDef.MAX_PLAYES; ++i)
		{
			mPlayerPieces[i].reset();
		}
		for(int i=0; i<ChessDef.NUM_PIECES; ++i)
		{
			mPieces[i] = null;
		}
		mMovesHistory.clear();
		mBoardParams.init(params);
	}
	
	//	Return whether there is a valid move that a piece at specific position can do
	public boolean hasValidMoves(int position)
	{
		Piece piece = piece(position); 
		if(piece == null)
		{
			return false;
		}
		List<Integer> moves = new ArrayList<Integer>();
		piece.moves(moves, this);
		return (moves.isEmpty() == false);
	}
	
	//	Return whether a piece of specific color could move from one position to another position.
	//	The method will check the following:
	//	1. The king isn't under attack.
	//	2. the "to" is empty or there is a piece with different color.
	@Override
	public boolean isValidMove(Piece fromPiece, int toPosition)
	{
		final Piece toPiece = piece(toPosition);
		final int fromPosition = fromPiece.position().position();
		if ((toPiece != null) && (toPiece.color() == fromPiece.color()))
		{
			return false;
		}
		
		//	Mark that new fake move is started.
		startNewPlayerMove(); 
		
		move(fromPosition, toPosition);
		Move extraMove = fromPiece.getExtraMove(this);
		if(extraMove != null)
		{
			move(extraMove.from(), extraMove.to());
		}
		
		//	check that as a result of this move the king isn't under attack.
		boolean valid = (isKingUnderAttack(fromPiece.color()) == false);

		//	revert the change of the fake move.	
		cancelLastPlayerMove(); 
		
		return valid;
	}
	
	//	return true if at position square there is a piece with different color.
	@Override
	public boolean isOpponentSquare(ChessDef.Color myColor, int position)
	{
		Piece piece = piece(position);
		return (piece != null && (piece.color() != myColor));
	}
	
	@Override
	public boolean isEmptySquare(int position)
	{
		Piece piece = piece(position);
		return (piece == null);	
	}
	
	@Override
	public boolean isPieceExist(Piece.Type type, ChessDef.Color color, int position)
	{
		Piece piece = piece(position);
		return ((piece != null) && (piece.color() == color) && (piece.type() == type));
	}
	
	@Override
	public boolean wasPieceMoved(int position)
	{
		Piece piece = piece(position);
		return ((piece != null) && piece.wasMoved());
	}
	
	public enum OpponentState
	{
		KING_IS_MATE,
		KING_IS_STALE,
		HAS_VALID_MOVES
	};
	public OpponentState opponentState(ChessDef.Color myColor)
	{
		int opponentColor = 1 - myColor.ordinal();
		int kingPosition = mPlayerPieces[opponentColor].king().position().position();
		Piece king  = piece(kingPosition);
		List<Integer> kingMoves = new ArrayList<Integer>();
		king.moves(kingMoves, this);
		boolean kingHasValidMoves = (kingMoves.size() != 0);
		if(kingHasValidMoves)
		{
			return OpponentState.HAS_VALID_MOVES;
		}
		
		boolean isKingUnderAttack = mPlayerPieces[myColor.ordinal()].isSquareUnderAttack(this, kingPosition);
		if(isKingUnderAttack)
		{
			return OpponentState.KING_IS_MATE;
		}
		boolean hasValidMoves = mPlayerPieces[opponentColor].hasValidMoves(this);
		return hasValidMoves ? OpponentState.HAS_VALID_MOVES : OpponentState.KING_IS_STALE;
	}
	
	public void add(Piece piece)
	{
		mPlayerPieces[piece.color().ordinal()].add(piece);
		mPieces[piece.position().position()] = piece;
	}
	
	public void remove(Piece piece)
	{
		if(piece != null)
		{
			mPlayerPieces[piece.color().ordinal()].remove(piece);
			mPieces[piece.position().position()] = null;
		}
	}
	
	//	Called when one player is going to move a piece.
	public void startNewPlayerMove()
	{
		mMovesHistory.push(new ArrayList<MoveInfo>());
	}
	private void cancelLastPlayerMove()
	{
		List<MoveInfo> moves = mMovesHistory.pop();
		Iterator<MoveInfo> moveIter = moves.iterator();
		while(moveIter.hasNext())
		{
			MoveInfo moveInfo = moveIter.next();
			Piece piece = moveInfo.piece();
			piece.restoreState(moveInfo.state());
			if(moveInfo.to() == ChessDef.KILL_MOVE)
			{
				add(piece);
			}
			else
			{
				mPieces[moveInfo.from()] = piece;
				mPieces[moveInfo.to()] = null;
			}
		}
	}
	
	public void move(int from, int to)
	{
		Piece piece = mPieces[from];
		mMovesHistory.peek().add(new MoveInfo(piece, to));
		if(to == ChessDef.KILL_MOVE)
		{
			mPlayerPieces[piece.color().ordinal()].remove(piece);
			mMovesHistory.peek().add(new MoveInfo(piece, ChessDef.KILL_MOVE));
		}
		else
		{
			if(mPieces[to] != null)
			{
				mPlayerPieces[mPieces[to].color().ordinal()].remove(mPieces[to]);
				mMovesHistory.peek().add(new MoveInfo(mPieces[to], ChessDef.KILL_MOVE));
			}
			mPieces[to] = piece;
		}
		mPieces[from] = null;
		piece.move(to);
	}
	
	public void promotePiece(Piece promotedPiece)
	{
		Piece oldPiece = piece(promotedPiece.position().position());
		promotedPiece.restoreState(oldPiece.state());
		remove(oldPiece);
		add(promotedPiece);
	}
	
	@Override
	public List<MoveInfo> currentMove()
	{
		return mMovesHistory.peek();
	}
	
	@Override
	public List<MoveInfo> previousMove()
	{
		if(mMovesHistory.size() <= 1)
		{
			return null;
		}
		List<MoveInfo> currMove = mMovesHistory.pop();
		List<MoveInfo> prevMove = mMovesHistory.peek();
		mMovesHistory.push(currMove);
		return prevMove;
	}
	
	public Piece piece(int position)
	{
		return (position < ChessDef.NUM_PIECES) ? mPieces[position] : null; 
	}
	
	@Override
	public boolean isKingUnderAttack(ChessDef.Color color)
	{
		int kingPosition = mPlayerPieces[color.ordinal()].king().position().position();
		return mPlayerPieces[1 - color.ordinal()].isSquareUnderAttack(this, kingPosition);
	}
	
	public BoardParams boardParams()
	{
		return mBoardParams;
	}
	
	public Move randomMove(ChessDef.Color color)
	{
		return mPlayerPieces[color.ordinal()].randomMove(this);
	}
	
	
	private PlayerPieces[] mPlayerPieces;
	private Piece[] mPieces;
	private Stack<List<MoveInfo>> mMovesHistory;
	private BoardParams mBoardParams;
}
